pub mod base;
pub mod client;
pub mod ecode;
pub mod file;
pub mod tbase;

use async_channel::{Receiver, Sender};
use chrono::prelude::*;
use serde_json::{json, Value};
use tracing::{instrument, warn};
use warp::{
    http::StatusCode,
    hyper::body::Bytes,
    reply::{self, Json, WithStatus},
};

pub use base::{cur_time, cur_time_timestamp, pre_cur_day, pre_cur_time, utc_offset_from_zero};
pub use ecode::{err_value, ECode};

pub type MapSv = serde_json::map::Map<String, Value>;
pub type VecV = Vec<Value>;
pub type Resp = Result<Value, Value>;
pub type RNull = Result<(), Value>;
pub type TmOffset = DateTime<FixedOffset>;
pub type TmLocal = DateTime<Local>;
pub type CSend = Sender<Value>;
pub type CRece = Receiver<Value>;
pub type SS = std::collections::HashMap<String, String>;

#[macro_export]
macro_rules! ok {
    () => {
        Ok(json!({
            "code": 1000,
            "success": true,
            "fail": false,
        }))
    };
    ($x: expr) => {
        Ok(json!({
            "code": 1000,
            "success": true,
            "fail": false,
            "data": $x,
        }))
    };
}

#[macro_export]
macro_rules! err {
    ($x: expr, $e: expr) => {
        Err(err_value($x, $e))
    };
}

#[macro_export]
macro_rules! throw_err {
    ($x: expr, $e: expr) => {
        return err!($x, $e);
    };
}

#[macro_export]
macro_rules! trace_warn {
    () => {
        let layer = tracing_subscriber::fmt::layer()
            .with_level(false)
            .compact()
            .with_filter(tracing_subscriber::filter::LevelFilter::WARN);
        tracing_subscriber::Registry::default().with(layer).init();
    };
}

#[macro_export]
macro_rules! cors {
    () => {
        warp::cors()
            .allow_any_origin()
            .allow_methods(vec!["GET", "POST", "OPTIONS"])
            .allow_credentials(true)
            .allow_headers(vec![
                "DNT",
                "X-CustomHeader",
                "Keep-Alive",
                "User-Agent",
                "X-Requested-With",
                "If-Modified-Since",
                "Cache-Control",
                "Content-Type",
            ])
    };
}

#[macro_export]
macro_rules! body {
    () => {
        warp::body::content_length_limit(1024 * 1024 * 1024).and(warp::body::bytes())
    };
}

#[macro_export]
macro_rules! opt {
    () => {
        warp::options().map(warp::reply).with(base::cors!())
    };
}

#[instrument(skip_all, name = "do_byte")]
pub fn do_byte(data: Bytes) -> Result<Value, Value> {
    if data.is_empty() {
        throw_err!(ECode::EmptyData, "post data is empty");
    }

    serde_json::from_slice(&data).or_else(|e| err!(ECode::JsonFormat, e))
}

#[instrument(skip_all, name = "do_str")]
pub fn do_str(data: &str) -> Result<Value, Value> {
    serde_json::from_str(data).or_else(|e| {
        warn!("{:?}", e);
        err!(ECode::JsonFormat, e)
    })
}

pub fn ret_ok(v: &Value) -> WithStatus<Json> {
    let ret = reply::json(v);
    reply::with_status(ret, StatusCode::OK)
}

#[instrument(skip_all, name = "ret_err")]
pub fn ret_err(v: &Value, code: Option<StatusCode>) -> WithStatus<Json> {
    warn!("error: {:?}", v);
    let ret = reply::json(v);

    let c = match code {
        Some(v) => v,
        None => StatusCode::EXPECTATION_FAILED,
    };

    reply::with_status(ret, c)
}
